﻿using Swifter.Tools;
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

namespace Swifter.Api
{
    public sealed unsafe class SignBuilder
    {
        const string Null = null;
        static readonly string True = true.ToString(CultureInfo.InvariantCulture);
        static readonly string False = false.ToString(CultureInfo.InvariantCulture);

        readonly HGlobalCache<char> hGCache = new HGlobalCache<char>();

        int offset;

        [MethodImpl(VersionDifferences.AggressiveInlining)]
        void Append(char c)
        {
            hGCache.GetPointer()[offset] = c;

            ++offset;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        void Append(string value)
        {
            var length = value.Length;

            Expand(length + 2);

            var pointer = hGCache.GetPointer() + offset;

            for (int i = 0; i < length; ++i)
            {
                pointer[i] = value[i];
            }

            offset += length;
        }

        public void Append(string name, string value)
        {
            Append(name);
            Append('=');

            if (value != null)
            {
                Append(value);
            }

            Append('&');
        }

        public void Append(string name, long value)
        {
            Append(name);
            Append('=');

            Expand(21);

            offset += NumberHelper.Decimal.ToString(value, hGCache.GetPointer() + offset);

            Append('&');
        }

        public void Append(string name, ulong value)
        {
            Append(name);
            Append('=');

            Expand(21);

            offset += NumberHelper.Decimal.ToString(value, hGCache.GetPointer() + offset);

            Append('&');
        }

        public void Append(string name, decimal value)
        {
            Append(name);
            Append('=');

            Expand(33);

            offset += NumberHelper.ToString(value, hGCache.GetPointer() + offset);

            Append('&');
        }

        public void Append(string name, DateTime datetime)
        {
            Append(name, new DateTimeOffset(datetime));
        }

        public void Append(string name, DateTimeOffset value)
        {
            Append(name, value.ToUnixTimeSeconds());
        }

        public void Append(string name, bool value)
        {
            Append(name);

            Append('=');

            Append(value ? True : False);
        }

        public void Append(string name, double value)
        {
            Append(name);
            Append('=');

            Expand(24);

            offset += NumberHelper.Decimal.ToString(value, hGCache.GetPointer() + offset);

            Append('&');
        }

        public void Append(string name, Guid value)
        {
            Append(name, value.ToString("N"));
        }

        public string ComputeSign()
        {
            hGCache.Count = Math.Max(offset - 1, 0);

            return hGCache.ComputeHash<MD5>();
        }

        public override string ToString()
        {
            hGCache.Count = Math.Max(offset - 1, 0);

            return hGCache.ToStringEx();
        }

        public void Clear()
        {
            offset = 0;

            hGCache.Count = 0;
        }

        public void Append(string name, object value)
        {
            if (value == null)
            {
                Append(name, Null);

                return;
            }

            if (value is DateTimeOffset dto)
            {
                Append(name, dto);

                return;
            }

            if (value is Guid guid)
            {
                Append(name, guid);

                return;
            }

            switch (Type.GetTypeCode(value.GetType()))
            {
                case TypeCode.Boolean:
                    Append(name, Convert.ToBoolean(value));
                    return;
                case TypeCode.Decimal:
                    Append(name, Convert.ToDecimal(value));
                    return;
                case TypeCode.Double:
                case TypeCode.Single:
                    Append(name, Convert.ToDouble(value));
                    return;
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                    Append(name, Convert.ToInt64(value));
                    return;
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    Append(name, Convert.ToUInt64(value));
                    return;
                case TypeCode.Char:
                case TypeCode.String:
                    Append(name, Convert.ToString(value));
                    return;
            }

            // We only calculate Sign for the underlying types.
        }

        [MethodImpl(VersionDifferences.AggressiveInlining)]
        void Expand(int expandMinSize)
        {
            if (hGCache.Capacity - offset < expandMinSize)
            {
                hGCache.Expand(expandMinSize);
            }
        }
    }
}
